package com.zy.explore.common.infrastructure.config;

import com.zy.explore.common.infrastructure.base.jpa.BaseEntity;
import com.zy.explore.common.infrastructure.base.jpa.BaseJpaRepository;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.ObjectProvider;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.jpa.repository.JpaRepository;

import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.Arrays;
import java.util.Map;
import java.util.stream.Collectors;
import java.util.stream.Stream;

/**
 * JPA充血模型配置类
 *
 * @author ChinaYin  主页:https://gitee.com/zy-explorej2ee 邮箱:zyexplorej2ee@sina.com
 * @date 2021年06月13日 14:44:18
 */
@Slf4j
@Configuration
@ConditionalOnClass(JpaRepository.class)
public class JpaRichDomainConfig {

    public JpaRichDomainConfig(ObjectProvider<BaseJpaRepository<?, ?>> repositories) {
        Map<Class<?>, BaseJpaRepository<?, ?>> repositoryMap = repositories.orderedStream()
                .flatMap(repository -> Arrays.stream(repository.getClass().getInterfaces())
                        .flatMap(repositoryType -> {
                            if (!repositoryType.isInterface()) {
                                return Stream.empty();
                            }

                            Type[] types = repositoryType.getGenericInterfaces();

                            if (types.length == 0) {
                                return Stream.empty();
                            }

                            for (Type type : types) {
                                if (type instanceof ParameterizedType) {
                                    ParameterizedType parameterized = (ParameterizedType) type;
                                    Type[] args = parameterized.getActualTypeArguments();

                                    if (args.length > 0 && parameterized.getRawType() == BaseJpaRepository.class && args[0] instanceof Class<?>) {
                                        return Stream.of(new Pair((Class<?>) args[0], repositoryType, repository));
                                    }
                                }
                            }
                            return Stream.empty();
                        })
                        .peek(it -> log.info("Found repository {} for entity {}", it.getRepositoryType().getSimpleName(), it.getEntityType().getSimpleName())))
                .collect(Collectors.toMap(Pair::getEntityType, Pair::getRepository));
        BaseEntity.initRepositories(repositoryMap);
    }

    @Data
    private static final class Pair {

        private final Class<?> entityType;

        private final Class<?> repositoryType;

        private final BaseJpaRepository<?, ?> repository;
    }
}
